/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.dao.support.mbt;

import com.google.common.collect.Lists;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.vacoor.nothing.common.util.Throwables;
import org.vacoor.nothing.common.validation.Groups;
import org.vacoor.nothing.common.validation.Validators;

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.groups.Default;
import java.util.List;
import java.util.Properties;

/**
 * Mybatis Bean validation plugin
 *
 * @author vacoor
 */
@Intercepts({
        @Signature(type = Executor.class, method = "update", args = {
                MappedStatement.class, Object.class
        })/*,
        @Signature(type = Executor.class, method = "query", args = {
                MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
        })
        */
})
public class BeanValidationInterceptor implements Interceptor {
    private static final Class<?>[] EMPTY_GROUPS = new Class[0];
    private static final Class<?>[] DEFAULT_RAW_GROUPS = new Class[]{Default.class};
    private static final Class<?>[] DEFAULT_INSERT_GROUPS = new Class[]{Groups.C.class};
    private static final Class<?>[] DEFAULT_UPDATE_GROUPS = new Class[]{Groups.U.class};
    private static final Class<?>[] DEFAULT_DELETE_GROUPS = new Class[]{Groups.D.class};

    private static final String INSERT_GROUPS_KEY = "validation.group.insert";
    private static final String UPDATE_GROUPS_KEY = "validation.group.update";
    private static final String DELETE_GROUPS_KEY = "validation.group.remove";

    private Class<?>[] INSERT_GROUPS = DEFAULT_INSERT_GROUPS;
    private Class<?>[] UPDATE_GROUPS = DEFAULT_UPDATE_GROUPS;
    private Class<?>[] REMOVE_GROUPS = DEFAULT_DELETE_GROUPS;
    private Validator validator;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        final Object[] args = invocation.getArgs();
        final MappedStatement ms = (MappedStatement) args[0];
        final SqlCommandType command = ms.getSqlCommandType();
        final Object parameter = args[1];

        Class<?>[] groups = EMPTY_GROUPS;
        switch (command) {
            case INSERT:
                groups = INSERT_GROUPS;
                break;
            case UPDATE:
                groups = UPDATE_GROUPS;
                break;
            case DELETE:
                groups = REMOVE_GROUPS;
                break;
            default:
                // ignore
        }

        if (0 < groups.length) {
            ensureValidator();
            Validators.validate(validator, parameter, groups);
        }

        return invocation.proceed();
    }

    private void ensureValidator() {
        if (null == validator) {
            validator = Validation.buildDefaultValidatorFactory().getValidator();
        }
    }


    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties props) {
        String igs = props.getProperty(INSERT_GROUPS_KEY);
        String ugs = props.getProperty(UPDATE_GROUPS_KEY);
        String rgs = props.getProperty(DELETE_GROUPS_KEY);

        INSERT_GROUPS = null == igs ? DEFAULT_INSERT_GROUPS : ("".equals(igs) ? EMPTY_GROUPS : parseGroups(igs));
        UPDATE_GROUPS = null == igs ? DEFAULT_UPDATE_GROUPS : ("".equals(ugs) ? EMPTY_GROUPS : parseGroups(ugs));
        REMOVE_GROUPS = null == igs ? DEFAULT_DELETE_GROUPS : ("".equals(rgs) ? EMPTY_GROUPS : parseGroups(rgs));
    }

    public Validator getValidator() {
        return validator;
    }

    public void setValidator(Validator validator) {
        this.validator = validator;
    }

    /* ***************************

     * ***************************/
    private Class<?>[] parseGroups(String groups) {
        String[] split = groups.split("\\s*,\\s*");
        List<Class<?>> gs = Lists.newArrayList();
        for (String group : split) {
            gs.add(loadClass(group));
        }
        return gs.toArray(new Class[gs.size()]);
    }

    private Class<?> loadClass(String name) {
        try {
            try {
                ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
                if (contextClassLoader != null) {
                    return contextClassLoader.loadClass(name);
                }
            } catch (Throwable ignore) {
            }
            return Class.forName(name, true, getClass().getClassLoader());
        } catch (ClassNotFoundException e) {
            throw Throwables.unchecked(e);
        }
    }
}
